<?php

defined('IN_MY_PHP') || die(0);
defined('PROTOCOL') || define('PROTOCOL', ($_SERVER['HTTPS'] == 'off' || $_SERVER['HTTPS'] == '') ? 'http://' : 'https://');
// for php v5.2~v5.3
if (!function_exists('session_status')) {
	define('PHP_SESSION_ACTIVE', 2);
	function session_status()
	{
		return session_id() === '' ? 1 : 2;
	}
}

/**
 * Session管理类，注意Sessoin固定攻击的使用.
 *
 * @author netmou <leiyanfo@sina.com>
 */
class Session
{
	const SESSION_NAME = 'PHPSESSID';
	const SESSION_EXPIRE = 180;
	// 本次请求验证通过的 token
	public $verified_token = null;
	public function start($sid = null, $keep = 1)
	{
		if (session_status() !== PHP_SESSION_ACTIVE) {
			$func = new Utility();
			$func->headerSent(true);
			$unix_time = time();
			// 设定服务端的会话数据存储的时长
			ini_set("session.gc_maxlifetime", self::SESSION_EXPIRE+1);
			
			// 设定浏览器端的缓存方式和失效时长
			session_cache_limiter('private, must-revalidate');
			session_cache_expire(self::SESSION_EXPIRE);
		
			$cur_params = session_get_cookie_params();
			session_set_cookie_params(
				$cur_params["lifetime"],
				$cur_params["path"],
				$cur_params["domain"],
				(PROTOCOL == 'https://')? true : false,
				true // 设置 httponly 标记
			);
			if ($sid != null) {
				session_id($sid);
			}
			session_name(self::SESSION_NAME);
			session_start();

			$cur_env = md5($_SERVER['HTTP_USER_AGENT'] . $func->getIPAddr());
			$access_times = $this->get('_access_times', 0);
			if($access_times == 0){
				$this->set('_last_session_env', $cur_env);
				$this->set('_session_start_time', $unix_time);
			}else{
				$last_access = $this->get('_last_access_time', 0);
				if($unix_time - $last_access > self::SESSION_EXPIRE * 60){
					$this->appClear();
					header("Content-type:text/html;charset=utf-8");
					exit('抱歉，您离开太久需要重新登陆！');
				}
				$last_env = $this->get('_last_session_env', null);
				if($last_env != $cur_env){
					$this->appClear();
					header("Content-type:text/html;charset=utf-8");
					exit('抱歉，您的网络或浏览器标识等发生变化需要重新登陆！');
				}
			
			}
			// 页面访问次数 +1
			$this->set('_access_times', $access_times + 1);
			$this->set('_last_access_time', $unix_time);
			$this->flashClean($keep); //清理无用的 flash session
		}
	}

	public function commit()
	{
		if (session_status() === PHP_SESSION_ACTIVE) {
			session_write_close();
		}
	}

	public function has($key, $appid = APPID)
	{
		$this->start();

		return isset($_SESSION[$appid . $key]);
	}

	public function set($key, $val, $appid = APPID)
	{
		$this->start();
		$_SESSION[$appid . $key] = $val;
	}

	public function delete($key, $appid = APPID)
	{
		$this->start();
		unset($_SESSION[$appid . $key]);
	}

	public function get($key, $def = null, $appid = APPID)
	{
		$this->start();
		$val = $_SESSION[$appid . $key];
		if ($val === null) {
			return $def;
		}

		return $val;
	}

	
	private function flashClean($keep = 1, $appid = APPID)
	{
		$counter = $this->get('_access_times', 0, $appid);
		$pattern = '/^_flash:(\\d+)_(?:.+)$/s';
		//将数据暂存一下，杜绝在遍历中修改被遍历的数组
		$storeData = is_array($_SESSION)? $_SESSION : array();
		foreach ($storeData as $key => &$val) {
			$match = array();
			if (preg_match($pattern, $key, $match)) {
				if ($match[1] < $counter - $keep) {
					$this->delete($match[0], $appid);
				}
			}
		}
	}
	// 存储临时信息（只保留当前和上次会话的）
	public function flashSet($key, $val, $appid = APPID)
	{
		$count = $this->get('_access_times', 0, $appid);
		$this->set("_flash:{$count}_{$key}", $val, $appid);
	}

	//获取会话的临时信息
	public function flashGet($key, $def = null, $backward = 1, $appid = APPID)
	{
		$count = $this->get('_access_times', 0, $appid) - $backward;
		if ($count > 0) {
			return $this->get("_flash:{$count}_{$key}", $def, $appid);
		}

		return $def;
	}
	//获取会话的开始时间
	public function getStartTime($appid = APPID)
	{
		return $this->get('_session_start_time', 0, $appid);
	}
	public function getSessionId()
	{
		$this->start();

		return session_id();
	}

	public function abort()
	{
		if (session_status() === PHP_SESSION_ACTIVE) {
			session_abort();
		}
	}
	public function verifyToken($ftk_id, $tokenStr, $appid = APPID)
	{

		$ftk_val = $this->get($ftk_id, null, $appid);
		if ($tokenStr != null && $ftk_val == $tokenStr) {
			$this->delete($ftk_id, $appid);
			$this->verified_token = $ftk_id;

			return true;
		}

		return false;
	}
	public function isVerified($token_id = null){
		if($token_id != null){
			// 前五位【_ftk_】，后六位【顺序号+随机码】
			return substr($this->verified_token, 5, -6) == $token_id;
		}
		return $this->verified_token != null;
	}
	// 在POST表单中含有_token_关键字的令牌信息，会在入口处自动验证
	public function formTokenInput($token_id = null, $appid = APPID){
		static $index = 0;
		$ftk_id = '_ftk_' . $token_id;
		// 如果指定了ID则不能存在重复的token
		if($token_id != null){
			$search = $appid . $ftk_id;
			$len = strlen($search);
			$storeData = is_array($_SESSION)? $_SESSION : array();
			foreach($storeData as $key => &$val){
				if(substr($key, 0, $len) == $search){
					unset($_SESSION[$key]);
				}
			}
		}
		$token_key = $ftk_id . sprintf('%03d', ++$index) . rand(100, 999);
		$token_str = md5(uniqid('-ftk-', true));
		$this->set($token_key, $token_str, $appid);
		
		return "<input type=\"hidden\" name=\"{$token_key}\" value=\"{$token_str}\">";
	}

	// 更新session_id
	public function updateId($del = true)
	{
		$this->start();
		session_regenerate_id($del);
	}

	//清空当前应用的会话
	public function appClear($appid = APPID)
	{
		$this->start();
		$appid_len = strlen($appid);
		if($appid_len == 0){
			$_SESSION = array();
			return null;
		}
		$storeData = is_array($_SESSION)? $_SESSION : array();
		foreach ($storeData as $key => &$val) {
			if ($appid == substr($key, 0, $appid_len)) {
				unset($_SESSION[$key]);
			}
		}
	}
}